import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as cheerio from 'cheerio';
import { cleanHtmlEntities } from 'src/utils/parseHTML';
import { transformArrayToString } from 'src/utils/format';
import { isNotEmptyArray, getResultByDecode } from 'src/utils/common';
import { MovieModuleType } from 'src/types/movieModule';
import { MovieEntity } from '../../entities/movie.entity';
import { MovieDownloadEntity } from '../../entities/movieDownload.entity';
import { AxiosService } from '../axiosModule/axiosService';
import { CommonService } from '../share/services/common.service';
import { ParallercontrolService } from '../share/services/parallercontrol.service';
import { LoggerService } from '../logger/logger.service';
let index = 0;
@Injectable()
export class Crawler6VService {
  private readonly decodeType = 'gb2312';
  private recentUrl: string;
  private imdb250Url: string;
  private baseUrl: string;
  private classicUrl: string;
  private mjUrl: string;
  private rjUrl: string;
  private gjUrl: string;
  private zyUrl: string;
  constructor(
    private readonly axiosService: AxiosService,
    private readonly configService: ConfigService,
    private readonly commonService: CommonService,
    private readonly parallercontrolService: ParallercontrolService,
    private readonly loggerService: LoggerService,
    @InjectRepository(MovieEntity)
    private readonly movieRepository: Repository<MovieEntity>,
    @InjectRepository(MovieDownloadEntity)
    private readonly movieDownloadRepository: Repository<MovieDownloadEntity>,
  ) {
    this.initUrl();
  }
  async onModuleInit() {
    // 获取最近电影
    // await this.getRecentMovieList();
    this.loggerService.log('最近电影获取完成');
    // 获取imdb250电影
    // await this.getMovieListByMovieModuleType(
    //   this.imdb250Url,
    //   MovieModuleType.IMDB250,
    // );
    this.loggerService.log('imdb250电影获取完成');
    // 获取经典高清电影
    // await this.getMovieListByMovieModuleType(
    //   this.classicUrl,
    //   MovieModuleType.CLASSIC,
    // );
    this.loggerService.log('经典高清电影获取完成','All');

    // await this.getMovieListByMovieModuleType(
    //   this.mjUrl,
    //   MovieModuleType.AMERICAN_TV,
    // );
    this.loggerService.log('美剧获取完成');

    // await this.getMovieListByMovieModuleType(
    //   this.rjUrl,
    //   MovieModuleType.JAPANKOREA_TV,
    // );
    this.loggerService.log('日剧获取完成');

    // await this.getMovieListByMovieModuleType(
    //   this.gjUrl,
    //   MovieModuleType.CHINESE_TV,
    // );
    this.loggerService.log('国剧获取完成');

    // await this.getMovieListByMovieModuleType(this.zyUrl, MovieModuleType.SHOW);
    this.loggerService.log('综艺获取完成');
  }

  private initUrl() {
    // 基础url
    this.baseUrl = this.configService.get('MOVIE_BASE_URL');
    // 最近最新电影
    this.recentUrl = this.configService.get('MOVIE_RECENT_URL');
    // imdb250电影
    this.imdb250Url = this.configService.get('MOVIE_BASE_IMDB');
    // 经典高清电影
    this.classicUrl = this.configService.get('MOVIE_BASE_ClASSIC');
    // 美剧
    this.mjUrl = this.configService.get('MOVIE_BASE_MJ');
    // 日剧
    this.rjUrl = this.configService.get('MOVIE_BASE_RJ');
    // 国剧
    this.gjUrl = this.configService.get('MOVIE_BASE_GJ');
    // 综艺
    this.zyUrl = this.configService.get('MOVIE_BASE_ZY');
  }

  // handle --> get --> save
  private async handleMovieDetail(
    detailUrl: string,
    publisthTime: string,
    movieModuleType: MovieModuleType,
  ) {
    const completeUrl = this.baseUrl + detailUrl;
    const detailRes = await this.commonService.retryWithRequest(() =>
      this.getMovieDetail(completeUrl),
    );
    index++;
    console.log(`正在处理第 ${index} 个电影`);
    if (!detailRes) {
      this.loggerService.log('没有电影信息', completeUrl);
      return;
    }
    if (!isNotEmptyArray(detailRes.downloadUrls)) {
      this.loggerService.log('没有电影下载地址', completeUrl);
      return;
    }

    return this.saveMovieDetail({
      ...detailRes,
      publisthTime,
      movieModuleType,
      resourceUrl: completeUrl,
    });
  }

  private async saveMovieDetail(movieDetailData: any) {
    const {
      cover,
      downloadUrls,
      title,
      introduction,
      publisthTime,
      movieName,
      category,
      country,
      language,
      releaseDate,
      movieLength,
      director,
      actors,
      movieModuleType,
      resourceUrl,
    } = movieDetailData;
    const movieEntity = this.movieRepository.create({
      cover,
      introduction,
      title,
      publisthTime,
      movieName,
      country,
      language,
      releaseDate,
      movieLength,
      director,
      movieModuleType,
      resourceUrl,
      actors: transformArrayToString(actors),
      category: transformArrayToString(category),
    });
    // 保存下载地址
    movieEntity.downloadUrls = downloadUrls.map((download) => {
      return this.movieDownloadRepository.create({
        ...download,
      });
    });
    await this.movieRepository.save(movieEntity);
  }

  private parseMovieData(data: string) {
    try {
      if (typeof data !== 'string') {
        return false;
      }
      // 按空行分割电影数据块
      const movieBlocks = data
        .split(/\n\s*\n/)
        .filter((block) => block.trim().length > 0);

      const movie = {} as any;
      // 解析每个电影数据块
      movieBlocks.forEach((block) => {
        // 先把所有的换行<br/>去掉
        block = block.replace(/<br\s*\/?>/g, '\n');
        // 提取片名
        let titleMatch = block.match(/◎片\s+名\s+([^\n]+)/);
        if (!titleMatch) {
          titleMatch = block.match(/◎标\s+题\s+([^\n]+)/);
        }
        if (titleMatch) {
          movie.movieName = cleanHtmlEntities(titleMatch[1]);
        }

        // 提取产地
        const countryMatch = block.match(/◎产\s+地\s+([^\n]+)/);
        if (countryMatch) {
          movie.country = cleanHtmlEntities(countryMatch[1]);
        }

        // 提取类别
        const categoryMatch = block.match(/◎类\s+别\s+([^\n]+)/);
        if (categoryMatch) {
          movie.category = cleanHtmlEntities(categoryMatch[1])
            .split('\/')
            .map((cat) => cat.trim());
        }

        // 提取语言
        const languageMatch = block.match(/◎语\s+言\s+([^\n]+)/);
        if (languageMatch) {
          movie.language = cleanHtmlEntities(languageMatch[1]);
        }

        // 提取上映日期
        let releaseDateMatch = block.match(/◎上映日期\s+([^\n]+)/);
        if (!releaseDateMatch) {
          releaseDateMatch = block.match(/◎年\s+代\s+([^\n]+)/);
        }
        if (releaseDateMatch) {
          movie.releaseDate = cleanHtmlEntities(releaseDateMatch[1]);
        }

        // 提取片长
        const lengthMatch = block.match(/◎片\s+长\s+([^\n]+)/);
        if (lengthMatch) {
          movie.movieLength = cleanHtmlEntities(lengthMatch[1]);
        }

        // 提取导演
        const directorMatch = block.match(/◎导\s+演\s+([^\n]+)/);
        if (directorMatch) {
          movie.director = cleanHtmlEntities(directorMatch[1]);
        }

        // 提取主演
        let actorsMatch = block.match(/◎主\s+演\s+([\s\S]*?)(?:◎|$)/);
        if (!actorsMatch) {
          actorsMatch = block.match(/◎演\s+员\s+([\s\S]*?)(?:◎|$)/);
        }
        if (actorsMatch) {
          // 提取所有主演行
          const actorsText = actorsMatch[1];
          // 分割主演，处理多行主演情况
          const actors = [];
          const actorLines = actorsText
            .split('\n')
            .filter((line) => line.trim().length > 0);

          actorLines.forEach((line) => {
            // 去掉每行开头的空白符和特殊符号，并清理HTML实体
            const cleanLine = cleanHtmlEntities(
              line.replace(/^\s*[　\s]+/, '').trim(),
            );
            if (cleanLine) {
              actors.push(cleanLine);
            }
          });

          movie.actors = actors;
        }

        // 提取简介
        const introMatch = block.match(/◎简\s+介\s+([\s\S]*?)(?:◎|$)/);
        if (introMatch) {
          movie.introduction = cleanHtmlEntities(
            introMatch[1].replace(/^\s*[　\s]+/gm, '').trim(),
          );
        }
      });

      return movie;
    } catch (error) {
      console.error('解析电影数据时出错:', error);
      return false;
    }
  }

  // 最新电影
  private async getRecentMovieList() {
    try {
      const decodedData = await this.getMoviePageByUrl(this.recentUrl);
      this.getMoveieList(decodedData, MovieModuleType.HOT);
    } catch (error) {
      // console.log(error);
      this.loggerService.logHttpRequestError(
        error.message,
        this.recentUrl,
        error.code,
      );
    }
  }

  // 获取标签(模块)下的电影
  async getMovieListByMovieModuleType(
    url: string,
    movieModuleType: MovieModuleType,
  ) {
    try {
      const decodedData = await this.commonService.retryWithRequest(() =>
        this.getMoviePageByUrl(url),
      );
      if (typeof decodedData !== 'string') {
        return;
      }
      // 分页有几页, 就有几个url
      const pageUrlList = this.parsePaginationHTML(decodedData);
      if (!isNotEmptyArray(pageUrlList)) {
        return;
      }
      // 开始控制并发
      // 这里的并发是获取每个分页下的电影列表url
      const pageTaskList = pageUrlList.map((pageUrl) => {
        return async () => {
          const url = this.baseUrl + pageUrl;
          const decodedData = await this.getMoviePageByUrl(url);
          const movieListUrls = this.parseMovieUrlsByHtml(
            decodedData as string,
          );
          return movieListUrls;
        };
      });
      const allMovieListUrls =
        await this.parallercontrolService.run(pageTaskList);
      const flatAllMovieListUrls = allMovieListUrls.flat();
      if (!isNotEmptyArray(flatAllMovieListUrls)) {
        return;
      }
      const movieTaskList = flatAllMovieListUrls.map((item) => {
        return () => {
          return this.handleMovieDetail(
            item.href,
            item.publisthTime,
            movieModuleType,
          );
        };
      });
      // 执行每个电影资源获取
      this.parallercontrolService.run(movieTaskList, 3, true);
    } catch (error) {
      // console.log(error);
      this.loggerService.logHttpRequestError(
        error.message,
        error.url,
        error.code,
      );
    }
  }

  // 具体的某个电影列表页
  private async getMoveieList(
    decodedData: unknown,
    movieModuleType: MovieModuleType,
  ) {
    if (typeof decodedData !== 'string') {
      return;
    }
    const movieListUrls = this.parseMovieUrlsByHtml(decodedData);
    if (!isNotEmptyArray(movieListUrls)) {
      return;
    }
    movieListUrls.forEach((item) => {
      this.handleMovieDetail(item.href, item.publisthTime, movieModuleType);
    });
  }

  // 根据url爬取电影列表, 并对爬取的内容进行编码
  private async getMoviePageByUrl(movieUrl: string) {
    try {
      const result = await this.commonService.retryWithRequest(() => {
        return this.axiosService.get(movieUrl, {
          // 配置无需对请求的内容解码
          responseType: 'arraybuffer',
        });
      });
      const decodedData = getResultByDecode(result.data, this.decodeType);
      return decodedData;
    } catch (error) {
      // console.log(error);
      this.loggerService.logHttpRequestError(
        error.message,
        movieUrl,
        error.code,
      );
    }
  }

  // 6v电影网, 分页的类型的html解析
  private parsePaginationHTML(html: string) {
    const $ = cheerio.load(html);
    // 获取id为main标签下的第一个div类为listpage下的select标签下所有option的value值
    const listpage = $('#main').find('.listpage').first();
    const selectOptions = listpage.find('select option');
    const options: string[] = [];
    selectOptions.each((_, element) => {
      const value = $(element).attr('value');
      if (value) {
        options.push(value);
      }
    });
    return options;
  }

  private async getMovieDetail(detailUrl: string) {
    if (!detailUrl) {
      return {};
    }
    // 拼装新的爬虫连接
    const detailRes = await this.axiosService.get(detailUrl, {
      // 配置无需对请求的内容解码
      responseType: 'arraybuffer',
    });
    const decodedRes = getResultByDecode(detailRes.data, this.decodeType);
    const detailMovieData = this.getMovieDetailByHtml(decodedRes as string);
    return detailMovieData;
  }

  private parseMovieUrlsByHtml(html: string) {
    // 获取最新电影列表
    const $ = cheerio.load(html);
    let liElements = $('.col4 .list').find('li') as any;
    const movieListUrls = [];
    liElements.each((_, element) => {
      // 转化为chereio对象
      const $li = $(element);
      // 提取爬取详情的链接
      const href = $li.find('a').attr('href') || '';
      const publisthTime = $li.find('span').text() || '';
      movieListUrls.push({
        href,
        publisthTime,
      });
    });
    return movieListUrls;
  }

  private getMovieDetailByHtml(html: string) {
    const $ = cheerio.load(html);
    // 类名为box的第一个h1元素内容
    const title = $('.box').find('h1').first().text();
    if (title && title.includes('佳片推荐')) {
      return false;
    }
    // id为endText元素中的第一个img标签的src属性值
    const cover = $('#endText').find('p').first().find('img').attr('src');
    // head标签里的meta标签属性name=description, content属性值
    let content = $('meta[name="description"]').attr('content');
    // 提取content的内容, 组成需要的数据
    const movieData = this.parseMovieData(content);
    if (!movieData) return;
    if (!movieData?.introduction) {
      // id为endText元素中的第二个p标签的内容
      let introduction = $('#endText').find('p').eq(1).text() || '';
      if (introduction) {
        if (introduction.includes('◎简')) {
          // 如果只有简介, 则需要获取下一个p标签的内容
          if (introduction === '◎简　　介') {
            introduction = $('#endText').find('p').eq(2).text() || '';
          } else {
            introduction = introduction.replace(/◎简　　介/g, '');
          }
        } else {
          introduction = $('#endText').find('p').eq(2).text() || '';
          if (introduction === '◎简　　介') {
            introduction = $('#endText').find('p').eq(3).text() || '';
          } else {
            introduction = introduction.replace(/◎简　　介/g, '');
          }
        }
      }
      movieData.introduction = introduction;
    }
    const downloadUrls = $('#endText')
      .find('table')
      .find('a')
      .map((_, element) => {
        const href = $(element).attr('href') || '';
        const text = $(element).text().trim();
        return { href, text, title };
      })
      // 使用get()方法将Cheerio对象转换为普通数组, 并把6v的地址过滤掉
      .get()
      .filter(
        (item: any) =>
          !item.href.endsWith('.html') && !item.href.startsWith('ftp'),
      );
    const movieDetail = { cover, downloadUrls, title, ...movieData };

    return movieDetail;
  }
}
